package org.apache.shardingsphere.transaction.base.seata.at;


import com.google.common.base.Preconditions;
import io.seata.config.FileConfiguration;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.rpc.netty.RmNettyRemotingClient;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
import io.seata.rm.RMClient;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.tm.TMClient;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.SneakyThrows;
import org.apache.shardingsphere.infra.database.type.DatabaseType;
import org.apache.shardingsphere.transaction.core.ResourceDataSource;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.apache.shardingsphere.transaction.spi.ShardingSphereTransactionManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: SeataATShardingSphereTransactionManager
 * @Author: 草莓爱上啵啵
 *
 *  定义了一个公共类 SeataATShardingSphereTransactionManager，实现了 ShardingSphereTransactionManager 接口
 *  用于在 ShardingSphere 中集成 Seata 的 AT (Automatic Transaction) 模式事务管理
 *
 *  这个类是 ShardingSphere 与 Seata AT 模式集成的核心实现，主要功能包括：
 *
 * 从配置文件加载 Seata 相关配置
 * 初始化 Seata 客户端（TM 和 RM）
 * 包装原始数据源为 Seata 的代理数据源
 * 实现事务的开始、提交、回滚等操作
 * 提供事务状态检查和连接获取功能
 * 在关闭时清理资源
 * 通过这个类，ShardingSphere 可以利用 Seata 的 AT 模式实现分布式事务管理。
 */
public class SeataATShardingSphereTransactionManager implements ShardingSphereTransactionManager{

    /**
     * 存储数据源名称到代理数据源的映射
     * 使用 DataSourceProxy 包装原始数据源以实现 Seata 的事务管理
     */
    private final Map<String, DataSource> dataSourceMap = new HashMap<>();

    /**
     * Seata 客户端的应用 ID，用于标识应用程序
     */
    private final String applicationId;

    /**
     * Seata 事务服务组名称，用于区分不同的事务服务
     */
    private final String transactionServiceGroup;

    /**
     * 标志是否启用 Seata AT 模式事务
     */
    private final boolean enableSeataAT;

    /**
     * 全局事务超时时间（秒）
     */
    private final int globalTXTimeout;

    /**
     * 从 seata.conf 配置文件加载配置
     * 默认启用 Seata AT 模式（sharding.transaction.seata.at.enable 默认为 true）
     * 读取应用 ID（client.application.id，必填）
     * 读取事务服务组（client.transaction.service.group，默认为 "default"）
     * 读取全局事务超时时间（sharding.transaction.seata.tx.timeout，默认为 60 秒）
     */
    public SeataATShardingSphereTransactionManager() {
        FileConfiguration config = new FileConfiguration("seata.conf");
        enableSeataAT = config.getBoolean("sharding.transaction.seata.at.enable", true);
        applicationId = config.getConfig("client.application.id");
        transactionServiceGroup = config.getConfig("client.transaction.service.group", "default");
        globalTXTimeout = config.getInt("sharding.transaction.seata.tx.timeout", 60);
    }

    /**
     * 初始化事务管理器
     * 如果启用了 Seata AT 模式：
     * 初始化 Seata RPC 客户端（initSeataRPCClient）
     * 将所有数据源包装为 DataSourceProxy 并存入 dataSourceMap
     */
    @Override
    public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources, final String providerType) {
        if (enableSeataAT) {
            initSeataRPCClient();
            resourceDataSources.forEach(each -> dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource())));
        }
    }

    /**
     * 初始化 Seata 的 TM (Transaction Manager) 和 RM (Resource Manager) 客户端
     * 检查应用 ID 是否配置
     * 使用相同的应用 ID 和事务服务组初始化 TM 和 RM 客户端
     */
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    private void initSeataRPCClient() {
        Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file.");
        TMClient.init(applicationId, transactionServiceGroup);
        RMClient.init(applicationId, transactionServiceGroup);
    }

    /**
     * 返回事务类型为 BASE（基础事务类型）
     * @return
     */
    @Override
    public TransactionType getTransactionType() {
        return TransactionType.BASE;
    }

    /**
     * 检查当前是否在事务中
     * 如果 Seata AT 模式未启用则抛出异常
     * 通过检查 SeataTransactionHolder 中是否有当前事务来判断（注释掉的代码是旧版 Seata 的检查方式）
     * @return
     */
    @SuppressWarnings("AlibabaRemoveCommentedCode")
    @Override
    public boolean isInTransaction() {
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        //return null != RootContext.getXID();
        return null != SeataTransactionHolder.get();
    }

    /**
     * 获取指定数据库和数据源的连接
     * 如果 Seata AT 模式未启用则抛出异常
     * 从 dataSourceMap 中获取对应的代理数据源并返回连接
     * @param databaseName
     * @param dataSourceName
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection(final String databaseName, final String dataSourceName) throws SQLException {
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        return dataSourceMap.get(databaseName + "." + dataSourceName).getConnection();
    }

    /**
     * 开始事务，使用配置的全局事务超时时间
     */
    @Override
    public void begin() {
        begin(globalTXTimeout);
    }

    /**
     * 开始事务，可指定超时时间（秒）
     * 检查超时时间是否合法（必须 > 0）
     * 如果 Seata AT 模式未启用则抛出异常
     * 获取或创建当前全局事务
     * 开始事务（将超时时间转换为毫秒）
     * 将当前事务存入 SeataTransactionHolder
     * @param timeout
     */
    @Override
    @SneakyThrows(TransactionException.class)
    public void begin(final int timeout) {
        if (timeout < 0) {
            throw new TransactionException("timeout should more than 0s");
        }
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        globalTransaction.begin(timeout * 1000);
        SeataTransactionHolder.set(globalTransaction);
    }

    /**
     * 提交事务
     * 如果 Seata AT 模式未启用则抛出异常
     * 提交当前事务
     * 无论提交是否成功，最后都会清理事务持有者和 RootContext
     * @param rollbackOnly
     */
    @Override
    @SneakyThrows(TransactionException.class)
    public void commit(final boolean rollbackOnly) {
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        try {
            SeataTransactionHolder.get().commit();
        } finally {
            SeataTransactionHolder.clear();
            RootContext.unbind();
        }
    }

    /**
     * 回滚事务
     * 如果 Seata AT 模式未启用则抛出异常
     * 回滚当前事务
     * 无论回滚是否成功，最后都会清理事务持有者和 RootContext
     */
    @Override
    @SneakyThrows(TransactionException.class)
    public void rollback() {
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        try {
            SeataTransactionHolder.get().rollback();
        } finally {
            SeataTransactionHolder.clear();
            RootContext.unbind();
        }
    }

    /**
     * 关闭事务管理器
     * 清理数据源映射
     * 清理事务持有者
     * 销毁 RM 和 TM 的 Netty 远程通信客户端
     */
    @Override
    public void close() {
        dataSourceMap.clear();
        SeataTransactionHolder.clear();
        RmNettyRemotingClient.getInstance().destroy();
        TmNettyRemotingClient.getInstance().destroy();
    }
}
